#ifndef _H_ADB_
#define _H_ADB_

/**
   @file ADB.h
   
  @brief This file implements a connection class to the ADB daemon using a host shield.
  Any connection string of the form "tcp:1099" etc can be used, as described in
  the protocol: http://lxr.e2g.org/source/system/core/adb/protocol.txt
  
  It uses the Circuits@Home USB class with a small mod indicated by comment
  header (addition of a method inTransferX to return number of bytes transferred).

  Thanks to Niels Brouwers for his native C implementation of an ADB
  "microbridge" on the Arduino architecture, which provided the idea
  for this implementation.

  @author Sri Subramanian
 */

#include <stdint.h>
#include "Usb.h"

#define ADB_MAX_PAYLOAD 4096
#define ADB_MAX_BUFSIZE 256		// Max number of bytes transferred by read/write.
#define MAX_USB_EPS 8		// MAX number of USB End Points supported

// ADB commands
#define A_SYNC 0x434e5953
#define A_CNXN 0x4e584e43
#define A_OPEN 0x4e45504f
#define A_OKAY 0x59414b4f
#define A_CLSE 0x45534c43
#define A_WRTE 0x45545257

// ADB markers in USB interface
#define ADB_CLASS 0xff
#define ADB_SUBCLASS 0x42
#define ADB_PROTOCOL 0x1

#define ADB_USB_PACKETSIZE 0x40
#define ADB_DESTINATION_LENGTH 64

// USB errors
#define USB_ERROR_NAK 0x04

// Wait times
#define USB_CONFIGURATION_WAIT_TIME 500
#define ADB_HOST_CONNECTION_WAIT_TIME 1000
#define ADB_CONNECTION_WAIT_TIME 500
#define ADB_WRITE_WAIT_TIME 200

typedef struct USBdata
{
  USB handle;			// Handle to Usb class

  uint8_t address;
  uint8_t config;
  uint8_t interface;
  uint8_t inputEP;
  uint8_t outputEP;
  EP_RECORD epRec[ MAX_USB_EPS ];  //USB needs to be rewritten not to demand this.
public:
 USBdata() : handle() {}

} USBdata;

typedef struct
{
  uint32_t command;
  uint32_t arg0;		// 1st arg
  uint32_t arg1;		// 2nd arg
  uint32_t dataLen;		// Payload length (0 is allowed)
  uint32_t dataCRC;		// Checksum of data payload
  uint32_t magic;		// Command ^ 0xffffffff

} ADBmessage;

/**
   @brief: ADBconnection manages an ADB connection.
*/
class ADBconnection
{
  // USB data
  USBdata usb;
  bool usbConnected;

  // ADB data
  char destination[ADB_DESTINATION_LENGTH];
  uint32_t localID;
  uint32_t remoteID;
  uint16_t dataSize, dataRead;
  enum {CLOSED=0,HOSTCONNECT,OPEN,RECEIVING,WRITING} status;

  // last message data
  uint32_t msgCmd;
  uint32_t msgRemoteId;
  uint32_t msgDataLen;
  uint32_t msgDataCRC;

  // Data buffer
  char dataBuf[ADB_MAX_BUFSIZE];

private:
  void initUsb();
  bool usbConfiguring();
  uint8_t numDeviceDescriptors();
  bool foundADB();
  void getDescrConfig(USB_CONFIGURATION_DESCRIPTOR* cDesc);
  bool isADBInterface(USB_INTERFACE_DESCRIPTOR* iDesc);
  bool getADBDescrData(char* bufp);
  bool setDevConfig();
  bool hostConnect();
  bool sendHostConnect();
  bool send(uint32_t command, uint32_t arg0, uint32_t arg1,
	    uint32_t len, uint8_t* data);
  bool recvdHostConnectReply();
  bool recvData(uint16_t len, uint8_t* buf);
  bool recvMessage();
  bool recvdConnectReply();
  bool recvdWriteOK();
  void checkForMsgs();
  bool handleUsbErr(char* where, uint8_t rcode);
  void sendOK();

public:
  /**
    Constructor: 

    @param ADB connection string, e.g. "tcp:1099"
   */
  ADBconnection(char* destination);

  /**
     init() Must be called before making other calls.
     Detects USB interface, finds ADB device and makes a host-level
     connection with it. 

     @return true if able to make a host-level ADB connection.
   */
  bool init();

  /**
     connect() makes a software-level connection between this program
     and the the software at the other end of destination string
     specified on the ADB device.

     @returns true if connection successful.
   */
  bool connect();

  /**
     write() writes to connection without confirmation from
     ADB layer.

     @returns true if write through USB layer succeeded.
   */
  bool write(uint8_t * data, uint16_t length);
  
  /**
     write() writes to connection with confirmation from
     ADB layer. Note: no end-to-end guarantees.

     @returns true if write to ADB layer succeeded.
   */
  bool writeSync(uint8_t * data, uint16_t length);

  /**
     poll() must be called periodically to let the underlying USB layer
     do its thing and to open the connection if closed.
  */
  void poll();

  /**
     available() checks if data is available to be read.
     
     @return number of bytes available to be read.
   */
  uint16_t available();	

  /**
     read() reads from connection.

     @return returns number of bytes read, -ve if error
   */
  int read(uint16_t len, uint8_t* buf); 

  /**
     close() closes connection.
   */
  void close();

};

#endif
